package edu.hawaii.nearestneighbor.tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import edu.hawaii.nearestneighbor.util.DataPoint;
import edu.hawaii.nearestneighbor.util.NNutil;

/**
 * Provides testing for the NN utilities.
 * 
 * @author Robert Puckett
 * 
 */
public class TestNNutil {

  /**
   * Test the Euclidean distance method.
   */
  @Test
  public void testEuclideanDistance() {
    List<Double> a1 = new ArrayList<Double>();
    List<Double> a2 = new ArrayList<Double>();
    a1.add(0.0);
    a1.add(0.0);
    a1.add(0.0);
    DataPoint p1 = new DataPoint(a1, null);
    assertEquals("Testing distance #1:", 0.0, NNutil.euclideanDistance(p1, p1));
    a1.clear();
    a1.add(25.0);
    a2.add(16.0);
    a1.add(0.0);
    a2.add(0.0);
    p1 = new DataPoint(a1, null);
    DataPoint p2 = new DataPoint(a2, null);
    assertEquals("Testing distance #2:", 9.0, NNutil.euclideanDistance(p1, p2));
    a1.clear();
    a1.add(44.0);
    a1.add(11.0);
    a1.add(32.0);
    p1 = new DataPoint(a1, null);
    a2.clear();
    a2.add(0.0);
    a2.add(0.0);
    a2.add(0.0);
    p2 = new DataPoint(a2, null);
    assertEquals("Testing distance #3:", Math.sqrt(3081.0), NNutil.euclideanDistance(p1, p2));

  }

  /**
   * Tests the Cam-weighted distance method.
   */
  @Test
  public void testCamDistance() {
    fail("Not yet implemented");
  }

  /**
   * Tests the statistical confidence method.
   */
  @Test
  public void testStatConfidence() {
    List<DataPoint> data = new ArrayList<DataPoint>();
    DataPoint d1 = new DataPoint(null, 1);
    DataPoint d2 = new DataPoint(null, 2);
    data.add(d1);
    // assertEquals("Check for 100% stat confidence: ",1,NNutil.statConfidence(data));
    data.add(d2);
    // assertEquals("Check for 50% stat confidence: ",0.5,NNutil.statConfidence(data));
  }

  /**
   * Test the classification method.
   */
  @Test
  public void testClassify() {
    fail("Not yet implemented");
  }

  /**
   * 
   */
  @Test
  public void testDiffDataPointDataPoint() {
    fail("Not yet implemented");
  }

  /**
   * Tests the difference operation on lists of double.
   */
  @Test
  public void testDiffListOfDoubleListOfDouble() {
    List<Double> l1 = new ArrayList<Double>();
    List<Double> l2 = new ArrayList<Double>();
    l1.add(1.0);
    l1.add(2.0);
    l2.add(1.0);
    l2.add(1.0);
    List<Double> l3 = NNutil.diff(l1, l2);
    assertTrue("Check difference of double list", ((l3.get(0) == 0.0) && (l3.get(1) == 1.0)));
  }

  /**
   * Tests the mode function to ensure it returns correct results.
   */
  @Test
  public void testModeState() {
    List<DataPoint> data = new ArrayList<DataPoint>();
    DataPoint p1 = new DataPoint(null, 0);
    DataPoint p2 = new DataPoint(null, 1);
    DataPoint p3 = new DataPoint(null, 1);
    DataPoint p4 = new DataPoint(null, 4);
    data.add(p1);
    assertEquals("Test of mode function 1", 0, NNutil.modeState(data));
    data.add(p2);
    data.add(p3);
    assertEquals("Test of mode function 2", 1, NNutil.modeState(data));
    data.add(p3);
    data.add(p3);
    data.add(p3);
    data.add(p4);
    data.add(p4);
    data.add(p4);
    assertEquals("Test of mode function 3", 1, NNutil.modeState(data));
  }

  /**
   * Tests the included angle funtion.
   */
  @Test
  public void testCalcIncludedAngle() {
    List<Double> l1 = new ArrayList<Double>();
    l1.add(1.0);
    l1.add(0.0);
    List<Double> l2 = new ArrayList<Double>();
    l2.add(0.0);
    l2.add(1.0);
    double angle = NNutil.calcIncludedAngle(l1, l2);
    System.out.println(angle);
    assertEquals("Check included angle of simple right triangle", Math.PI / 2, angle);
    l1.clear();
    l2.clear();
    l1.add(1.0);
    l1.add(5.0);
    l1.add(15.0);
    l2.add(4.0);
    l2.add(3.0);
    l2.add(2.0);
    double l1mag = NNutil.magnitude(l1);
    double l2mag = NNutil.magnitude(l2);
    double dotprod = NNutil.dotProd(l1, l2);
    double cosang = dotprod / (l1mag * l2mag);
    angle = NNutil.calcIncludedAngle(l1, l2);
    assertEquals("Check included angle of more complex set of vectors 1", Math.acos(cosang), angle);
    l1.clear();
    l2.clear();
    l1.add(3.0);
    l1.add(3.0);
    l1.add(3.0);
    l2.add(2.0);
    l2.add(0.0);
    l2.add(-3.0);
    angle = NNutil.calcIncludedAngle(l1, l2);
    System.out.println("Function angle: " + angle);

    l1mag = NNutil.magnitude(l1);
    l2mag = NNutil.magnitude(l2);
    dotprod = NNutil.dotProd(l1, l2);
    cosang = dotprod / (l1mag * l2mag);
    assertEquals("Check on 2: magnitude 1", Math.sqrt(27.0), l1mag);
    assertEquals("Check on 2: magnitude 2", Math.sqrt(13.0), l2mag);
    assertEquals("Check on 2: dotproduct", (6.0 - 9.0), dotprod);

    assertEquals("Check included angle of more complex set of vectors 2", 80.78, Math.acos(angle));

    l1.clear();
    l2.clear();
    l1.add(0.6);
    l1.add(0.8);
    l2.add(0.8);
    l2.add(0.6);
    angle = NNutil.calcIncludedAngle(l1, l2);
    System.out.println("Function angle: " + angle);

    l1mag = NNutil.magnitude(l1);
    System.out.println("L1 mag: " + l1mag);
    l2mag = NNutil.magnitude(l2);
    System.out.println("L2 mag: " + l2mag);
    dotprod = NNutil.dotProd(l1, l2);
    System.out.println("Dot Product: " + dotprod);
    cosang = dotprod / (l1mag * l2mag);
    System.out.println("CosAng: " + cosang);
    assertEquals("Check on 2: magnitude 1", 1.0, l1mag);
    assertEquals("Check on 2: magnitude 2", 1.0, l2mag);
    assertEquals("Check on 2: dotproduct", 0.96, dotprod);

    assertEquals("Check included angle of more complex set of vectors 2", 0.283794109, Math
        .acos(angle));

  }

  /**
   * Tests to ensure that the function findKNearest finds the correct points.
   */
  @Test
  public void testFindKNearest() {

    List<DataPoint> data = new ArrayList<DataPoint>();

    List<Double> d1 = new ArrayList<Double>();
    d1.add(0.0);
    d1.add(0.0);
    d1.add(0.0);
    d1.add(0.0);
    DataPoint p1 = new DataPoint(d1, null);

    List<Double> d2 = new ArrayList<Double>();
    d2.add(1.0);
    d2.add(1.0);
    d2.add(1.0);
    d2.add(1.0);
    DataPoint p2 = new DataPoint(d2, null);

    List<Double> d3 = new ArrayList<Double>();
    d3.add(2.0);
    d3.add(2.0);
    d3.add(2.0);
    d3.add(2.0);
    DataPoint p3 = new DataPoint(d3, null);

    List<Double> d4 = new ArrayList<Double>();
    d4.add(3.0);
    d4.add(3.0);
    d4.add(3.0);
    d4.add(3.0);
    DataPoint p4 = new DataPoint(d4, null);

    data.add(p4);
    data.add(p1);
    data.add(p3);
    data.add(p2);

    List<DataPoint> near = NNutil.findKNearest(data, p1, 2);

    assertEquals("Testing findKNearest 1, size", 2, near.size());
    assertTrue("Testing findKNearest 1, contents 1", near.contains(p1));
    assertTrue("Testing findKNearest 1, contents 2", near.contains(p2));
  }

  /**
   * Tests to ensure that the function findKNearest finds the correct points.
   */
  @Test
  public void testDistanceSort() {

    // NNutil.Distance d1 = new Distance(81.0,null);
    // NNutil.Distance d2 = new Distance(2.0,null);
    // NNutil.Distance d3 = new Distance(4.0,null);
    // NNutil.Distance d4 = new Distance(110.0,null);
    // NNutil.Distance d5 = new Distance(30.0,null);
    // NNutil.Distance d6 = new Distance(4.0,null);
    // NNutil.Distance d7 = new Distance(0.0,null);
    // NNutil.Distance d8 = new Distance(4.0,null);
    // List<Distance> dlist = new ArrayList<Distance>();
    // dlist.add(d1);
    // dlist.add(d2);
    // dlist.add(d3);
    // dlist.add(d4);
    // dlist.add(d5);
    // dlist.add(d6);
    // dlist.add(d7);
    // dlist.add(d8);
    // boolean ordered = true;
    // Collections.sort(dlist);
    // double dlast = dlist.get(0).dist;
    // for(Distance c: dlist) {
    // if (c.dist < dlast) {
    // ordered = false;
    // }
    // }
    // //List<DataPoint> findKNearest(List<DataPoint> data, DataPoint test, int k);
    //    
    // assertTrue("Testing distance ordering",ordered);
  }

  /**
   * Tests to ensure that the function magnitude calculates correctly.
   */
  @Test
  public void testMagnitude() {
    List<Double> d1 = new ArrayList<Double>();
    d1.add(0.0);
    d1.add(0.0);
    d1.add(0.0);
    d1.add(0.0);

    List<Double> d2 = new ArrayList<Double>();
    d2.add(1.0);
    d2.add(2.0);
    d2.add(3.0);
    d2.add(4.0);

    List<Double> d3 = new ArrayList<Double>();
    d3.add(0.0);
    d3.add(2.0);
    d3.add(3.0);
    d3.add(4.0);

    double mag2 = Math.sqrt(1.0 + 4.0 + 9.0 + 16.0);
    double mag3 = Math.sqrt(4.0 + 9.0 + 16.0);

    assertEquals("Test magnitude 1", 0.0, NNutil.magnitude(d1));
    assertEquals("Test magnitude 2", mag2, NNutil.magnitude(d2));
    assertEquals("Test magnitude 3", mag3, NNutil.magnitude(d3));

  }

  /**
   * Tests the unit vector calculation.
   */
  @Test
  public void testCalcUnitVector() {
    List<Double> v = new ArrayList<Double>();
    v.add(1.0);
    List<Double> u = NNutil.calcUnitVector(v);
    assertEquals("Test Unit vector # 0", 1.0, u.get(0));
    assertEquals("Test Unit vector # 0, magnitude", 1.0, NNutil.magnitude(u));
    v.clear();
    u.clear();
    v.add(5.0);
    v.add(4.0);
    u = NNutil.calcUnitVector(v);
    assertEquals("Test Unit vector # 1", 5.0 / Math.sqrt(25.0 + 16.0), u.get(0));
    assertEquals("Test Unit vector # 1", 4.0 / Math.sqrt(25.0 + 16.0), u.get(1));
    assertEquals("Test Unit vector # 1, magnitude", 1.0, NNutil.magnitude(u));

  }

}
